package com.lcy.struct.bTreePlus.disk.core;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lcy.struct.bTreePlus.disk.index.BTreeIndex;
import com.lcy.struct.bTreePlus.disk.recovery.RecoveryCenter;
import com.lcy.struct.bTreePlus.entity.DiskPageInfo;
import com.lcy.struct.bTreePlus.property.BPlusFileInfo;
import com.lcy.struct.bTreePlus.disk.constants.BPlusPublicConstants;
import com.lcy.struct.bTreePlus.disk.file.FileUtil;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author lucongyang
 * @date Created in  16:43
 * @description 节点
 */
@Setter
@Getter
public class BPlusTree {
    /**
     * 当前节点的索引链
     */
    private List<BTreeIndex> indexList = new ArrayList<>();
    /**
     * 父节点
     */
    private Long parentPosition;
    /**
     * 子节点
     */
    private List<Long> childrenPosition;
    /**
     * 数据节点
     */
    private List<Object> dataList = null;
    /**
     * 父节点
     * 仅构树的时候用
     */
    private Long leftPosition;
    /**
     * 右节点
     */
    private Long rightPosition;
    /**
     * 磁盘上的位置
     */
    private Long position;

    /**
     * 分裂结果
     */
    public List<BPlusTree> splitResult() {
        List<BPlusTree> result = new ArrayList<>();
        int middleIndex = getMiddleIndex();

        BPlusTree left = new BPlusTree();
        BPlusTree middle = new BPlusTree();
        BPlusTree right = new BPlusTree();
        // 左子树
        List<BTreeIndex> leftIds = new ArrayList<>(this.indexList.subList(0, middleIndex));
        left.setIndexList(leftIds);
        // 中子树
        List<BTreeIndex> middleSingletonList = new ArrayList<>();
        middleSingletonList.add(this.indexList.get(middleIndex));
        middle.setIndexList(middleSingletonList);
        List<Long> middleChildren = new ArrayList<>();
        middleChildren.add(left.getPosition());
        middleChildren.add(right.getPosition());
        middle.setChildrenPosition(middleChildren);
        if (this.childrenPosition == null || this.childrenPosition.size() == 0) {
            // 如果是叶子节点
            // 右子树
            List<BTreeIndex> rightIds = new ArrayList<>(this.indexList.subList(middleIndex, this.indexList.size()));
            right.setIndexList(rightIds);
            // 加上数据节点
            left.setDataList(new ArrayList<>(this.dataList.subList(0, middleIndex)));
            right.setDataList(new ArrayList<>(this.dataList.subList(middleIndex, this.indexList.size())));
        } else {
            // 如果不是叶子节点
            List<BTreeIndex> rightIds = new ArrayList<>(this.indexList.subList(middleIndex + 1, this.indexList.size()));
            right.setIndexList(rightIds);
            int childMiddle = (int) (Math.ceil(this.childrenPosition.size() / 2.));
            left.setChildrenPosition(new ArrayList<>(this.childrenPosition.subList(0, childMiddle)));
            right.setChildrenPosition(new ArrayList<>(this.childrenPosition.subList(childMiddle, this.childrenPosition.size())));
        }
        // 给左右新节点分配空间
        left.setPosition(BPlusFileInfo.allocate(BPlusPublicConstants.PAGE_INFO_SIZE + BPlusPublicConstants.PAGE_SIZE));
        right.setPosition(BPlusFileInfo.allocate(BPlusPublicConstants.PAGE_INFO_SIZE + BPlusPublicConstants.PAGE_SIZE));
        // 执行完该方法后middle的子节点需要合并到新的节点的子节点中
        result.add(left);
        result.add(middle);
        result.add(right);
        return result;
    }

    public Long findChildren(int index) {
        return this.childrenPosition.get(index);
    }

    @JsonIgnore
    public boolean isLeaf() {
        return this.childrenPosition == null || this.childrenPosition.size() == 0;
    }

    @JsonIgnore
    public boolean isRoot() {
        return this.parentPosition == null;
    }

    private BTreeIndex findMiddle() {
        if (this.indexList == null || this.indexList.size() == 0) {
            return null;
        }
        if (this.indexList.size() < 2) {
            return indexList.get(0);
        }
        int middle = getMiddleIndex();
        return indexList.get(middle);
    }

    private Integer getMiddleIndex() {
        return indexList.size() / 2;
    }

    @Override
    public String toString() {
        String childrenIds = "";
        if (this.getChildrenPosition() != null && this.getChildrenPosition().size() > 0) {
            for (Long bPlusTree : this.childrenPosition) {
                childrenIds += bPlusTree + ";";
            }
        }
        return this.getIndexList() + "\tchildren=" + childrenIds;
    }

    public void setDataList(List<Object> dataList) {
        this.dataList = dataList;
    }

    public void addData(int index, Object data) {
        if (this.dataList == null) {
            this.dataList = new ArrayList<>();
        }
        this.dataList.add(index, data);
    }

    /**
     * 序列化，持久化到磁盘
     * 第一个节点页的前BPlusPublicConstants.PAGE_INFO_SIZE字节存第一页的页信息
     * 第一个节点的position=第一页的页信息的position
     */
    public void serializeToFile() {
        ObjectMapper objectMapper = RecoveryCenter.getInstance().objectMapper;
        byte[] serializedBytes = null;
        try {
            int size = BPlusPublicConstants.PAGE_SIZE;
            serializedBytes = objectMapper.writeValueAsBytes(this);

            int length = serializedBytes.length;
            int n = (int) (Math.ceil(length / (size + 0.)));
            List<DiskPageInfo> extentIndexInfoPositionList = new ArrayList<>();

            if (this.position == null) {
                this.position = BPlusFileInfo.getEndPosition();
                // 分配磁盘空间，数据区域+指针区域
                BPlusFileInfo.allocate(n * size + BPlusPublicConstants.PAGE_INFO_SIZE);
            }
            // 前 BPlusPublicConstants.PAGE_INFO_SIZE是第一个页信息
            long infoStartPosition = this.position;

            DiskPageInfo firstDiskPageInfo = new DiskPageInfo();
            firstDiskPageInfo.setSelfPosition(infoStartPosition);
            firstDiskPageInfo.setPosition(this.position + BPlusPublicConstants.PAGE_INFO_SIZE);
            extentIndexInfoPositionList.add(firstDiskPageInfo);
            serializedBytes = objectMapper.writeValueAsBytes(this);

            List<Long> extendPositionList = FileUtil.getInstance().rewrite(
                    this.position + BPlusPublicConstants.PAGE_INFO_SIZE, size, serializedBytes);

            if (extendPositionList != null && extendPositionList.size() > 0) {
                // 分配第二个开始的指针
                infoStartPosition = BPlusFileInfo.allocate(extendPositionList.size() * BPlusPublicConstants.PAGE_INFO_SIZE);

                for (int i = 0; i < extendPositionList.size(); i++) {
                    DiskPageInfo diskPageInfo = new DiskPageInfo();
                    diskPageInfo.setSelfPosition(infoStartPosition + i * BPlusPublicConstants.PAGE_INFO_SIZE);
                    diskPageInfo.setPosition(extendPositionList.get(i));
                    extentIndexInfoPositionList.add(diskPageInfo);

                    // 处理上个节点信息
                    extentIndexInfoPositionList.get(i).setNextPagePosition(diskPageInfo.getSelfPosition());
                }
            }

            //写入指针
            for (DiskPageInfo diskPageInfo : extentIndexInfoPositionList) {
                byte[] bytes = objectMapper.writeValueAsBytes(diskPageInfo);
                List<Long> modify = FileUtil.getInstance().rewrite(diskPageInfo.getSelfPosition(), BPlusPublicConstants.PAGE_INFO_SIZE, bytes);
                if (modify != null) {
                    throw new RuntimeException("指针长度超过" + BPlusPublicConstants.PAGE_INFO_SIZE);
                }
            }
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException("节点序列化失败");
        }
    }


    public static BPlusTree deserialize(long position) {
        long infoPosition = position;
        List<Long> bPlusTreeDiskInfo = new ArrayList<>();
        try {
            bPlusTreeDiskInfo = findBplusTreeDiskInfos(infoPosition);
        } catch (Exception ex) {
            throw new RuntimeException("反序列化DiskInfo错误，position=" + position, ex);
        }
        byte[] bPlusTreeBytes = new byte[BPlusPublicConstants.PAGE_SIZE * (bPlusTreeDiskInfo.size() + 1)];
        int bPlusTreeBytesIndex = 0;

        if (bPlusTreeDiskInfo.size() > 0) {
            for (Long pst : bPlusTreeDiskInfo) {
                byte[] nextBytes = FileUtil.getInstance().read(pst, BPlusPublicConstants.PAGE_SIZE);
                for (byte b : nextBytes) {
                    bPlusTreeBytes[bPlusTreeBytesIndex++] = b;
                }
            }
            try {
                return JSONObject.parseObject(bPlusTreeBytes, BPlusTree.class);
            } catch (Exception ex) {
                throw new RuntimeException("反序列化错误，position=" + position, ex);
            }
        }
        return null;
    }

    public boolean containsIndex(Comparable... index) {
        Integer indexOfIndex = indexOfIndex(index);
        return indexOfIndex != null;
    }

    public Integer indexOfIndex(Comparable... index) {
        BTreeIndex toCompare = new BTreeIndex();
        toCompare.setIndexList(Arrays.asList(index));
        return indexOfIndex(toCompare);
    }

    public Integer indexOfIndex(BTreeIndex toCompare) {
        // todo 二分算法改进
        for (int i = 0; i < this.indexList.size(); i++) {
            int compare = this.indexList.get(i).compare(toCompare);
            if (compare == 0) {
                return i;
            }
        }
        return null;
    }

    /**
     * 移除节点中的一项
     */
    public void removeItem(int i) {
        this.indexList.remove(i);
        if (this.dataList != null && this.dataList.size() > i) {
            this.dataList.remove(i);
        }
    }

    private static List<Long> findBplusTreeDiskInfos(long infoPosition) {
        List<Long> result = new ArrayList<>();
        byte[] infoBytes = FileUtil.getInstance().read(infoPosition, BPlusPublicConstants.PAGE_INFO_SIZE);
        if (infoBytes.length > 0) {
            DiskPageInfo diskPageInfo = null;
            try {
                diskPageInfo = JSONObject.parseObject(infoBytes, DiskPageInfo.class);
            } catch (Exception ex) {
                throw new RuntimeException("DiskPageInfo解析错误,DiskPageInfoPosition=" + infoPosition, ex);
            }
            if (diskPageInfo != null) {
                if (diskPageInfo.getPosition() != null) {
                    result.add(diskPageInfo.getPosition());
                }
                if (diskPageInfo.getNextPagePosition() != null) {
                    List<Long> list = findBplusTreeDiskInfos(diskPageInfo.getNextPagePosition());
                    if (list.size() > 0) {
                        result.addAll(list);
                    }
                }
            }
        }
        return result;
    }
}
